/* Copyright [c] 2018-2028 www.chungen90.com Allrights Reserved*/
#ifndef SDL_PLAYER_H
#define SDL_PLAYER_H
#include <string>
#include "pch.h"
#include <memory>
#include "CGSdlRender.h"
#include <thread>

namespace ChunGen{ namespace Client {namespace  Player
{
	enum  CGPlayState
	{
		PClose = 0,
		POpen,
		PStart
	};

	class SdlPlayer
	{
	public:
		SdlPlayer()
			:inputContext(nullptr)
			,lastReadPacktTime(av_gettime())
			,timeout(10)
			,sdlRender(nullptr)
			,videoIndex(-1)
			,audioIndex(-1)
			,playState(CGPlayState::PClose)
			,swr(nullptr)
		{

		}

		void Init()
		{
			av_register_all();
			avfilter_register_all();
			avformat_network_init();
			av_log_set_level(AV_LOG_ERROR);
		}

		int OpenInput(std::string inputUrl)  //rtsp://admin:admin12345@192.168.1.64:554/h264/ch1/main/av_stream
		{
			inputContext = avformat_alloc_context();	
			lastReadPacktTime = av_gettime();
			inputContext->interrupt_callback.opaque = this;
			inputContext->interrupt_callback.callback = interrupt_cb;
			int ret = avformat_open_input(&inputContext, inputUrl.c_str(), nullptr,nullptr);
			if(ret < 0)
			{
				av_log(NULL, AV_LOG_ERROR, "Input file open input failed\n");
				return  ret;
			}
			ret = avformat_find_stream_info(inputContext,nullptr);
			if(ret < 0)
			{
				av_log(NULL, AV_LOG_ERROR, "Find input file stream inform failed\n");
			}
			else
			{
				for(int i = 0; i < inputContext->nb_streams; i++)
				{
					if(inputContext->streams[i]->codec->codec_type == AVMediaType::AVMEDIA_TYPE_VIDEO)
					{
						videoIndex  = i;
					}
					else if(inputContext->streams[i]->codec->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO)
					{
						audioIndex = i;
					}
				}
				playState = CGPlayState::POpen;
				av_log(NULL, AV_LOG_FATAL, "Open input file  %s success\n",inputUrl.c_str());
			}
			return ret;
		}

		int  Play(HWND handle)
		{
			int ret = -1;
			if(playState != CGPlayState::POpen)  ret;

			sdlRender =std::make_shared<CGSDLRender>();
			if(videoIndex >= 0)
			{
				ret = initVideoDecodeContext();
				if(ret < 0) return ret;
				sdlRender->InitVideo(handle,0);
				sdlRender->CreateVideoSurface(inputContext->streams[videoIndex]->codec->width, inputContext->streams[videoIndex]->codec->height);
			}
			if(audioIndex >= 0)
			{ 	
				ret = initAudioDecodeContext();
				if(ret < 0) return ret;

				sdlRender->InitAudio(inputContext->streams[audioIndex]->codec->sample_rate,inputContext->streams[audioIndex]->codec->channels);			
			}
			playState = CGPlayState::PStart;
			task.swap(std::thread([=]{

				AVFrame * videoFrame = av_frame_alloc();
				AVFrame * audioFrame = av_frame_alloc();
				while(playState == CGPlayState::PStart)
				{
					auto packet = readPacketFromSource();
					if(packet)
					{
						if(packet->stream_index == videoIndex)
						{
							if(videoDecode(packet.get(),videoFrame))
							{
								playVideo(videoFrame);
							}
						}
						else if(packet->stream_index == audioIndex)
						{
							if(audioDecode(packet.get(),videoFrame))
							{
								playAudio(videoFrame);
							}
						}
					}

				}
				av_frame_free(&videoFrame);
				av_frame_free(&audioFrame);
			}));
			return ret;
		}
		void Close()
		 {
			 playState = CGPlayState::PClose;
			 if(sdlRender)
			 {
				 sdlRender->Close();
				 sdlRender = nullptr;
			 }
			 if(task.joinable())
			 {
				 task.join();
			 }
			if(inputContext)
			{
				for(int i = 0 ; i < inputContext->nb_streams; i++)
				{
					AVCodecContext *codecContext = inputContext->streams[i]->codec;
					avcodec_close(codecContext);
				}
				avformat_close_input(&inputContext);
				inputContext = nullptr;
			}
			if(swr)
			{
				swr_free(&swr);
				swr = nullptr;
			}
		 }
	private:
		static int interrupt_cb(void *ctx)
		{
			SdlPlayer* sdlPlayer = (SdlPlayer* )ctx;

			if(av_gettime() - sdlPlayer->lastReadPacktTime > sdlPlayer->timeout *1000 *1000)
			{
				return -1;
			}
			return 0;
		}
		std::shared_ptr<AVPacket> readPacketFromSource()
		{
			std::shared_ptr<AVPacket> packet(static_cast<AVPacket*>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) { av_packet_free(&p); av_freep(&p);});
			av_init_packet(packet.get());
			lastReadPacktTime = av_gettime();
			int ret = av_read_frame(inputContext, packet.get());
			if(ret >= 0)
			{
				return packet;
			}
			else
			{
				return nullptr;
			}

		}		

		int initVideoDecodeContext()
		{	
			auto codecId = inputContext->streams[videoIndex]->codec->codec_id;
			auto codec = avcodec_find_decoder(codecId);
			if (!codec)
			{
				return -1;
			}

			int ret = avcodec_open2(inputContext->streams[videoIndex]->codec, codec, NULL);
			return ret;

		}

		bool videoDecode(AVPacket* packet, AVFrame *frame)
		{
			int gotFrame = 0;
			auto hr = avcodec_decode_video2(inputContext->streams[videoIndex]->codec, frame, &gotFrame, packet);
			if (hr >= 0 && gotFrame != 0)
			{
				return true;
			}
			return false;
		}

		int initAudioDecodeContext()
		{	
			int ret  = -1;
			auto codecId = inputContext->streams[audioIndex]->codec->codec_id;
			auto codec = avcodec_find_decoder(codecId);
			if (!codec)
			{
				return ret;
			}

			ret = avcodec_open2(inputContext->streams[audioIndex]->codec, codec, NULL);
			if(ret < 0) return ret;

			if(inputContext->streams[audioIndex]->codec->sample_fmt != AV_SAMPLE_FMT_S16)
			{
				swr = swr_alloc();
				av_opt_set_int(swr, "in_channel_layout", inputContext->streams[audioIndex]->codec->channel_layout, 0);
				av_opt_set_int(swr, "out_channel_layout", inputContext->streams[audioIndex]->codec->channel_layout,  0);
				av_opt_set_int(swr, "in_sample_rate",     inputContext->streams[audioIndex]->codec->sample_rate, 0);
				av_opt_set_int(swr, "out_sample_rate",    inputContext->streams[audioIndex]->codec->sample_rate, 0);
				av_opt_set_sample_fmt(swr, "in_sample_fmt",  inputContext->streams[audioIndex]->codec->sample_fmt, 0);
				av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16,  0);
				swr_init(swr);
			}		
			return ret;
		}

		bool audioDecode(AVPacket* packet, AVFrame *frame)
		{
			int gotFrame = 0;
			auto hr = avcodec_decode_audio4(inputContext->streams[audioIndex]->codec, frame, &gotFrame, packet);
			if (hr >= 0 && gotFrame != 0)
			{
				return true;
			}
			return false;
		}

		void playVideo(AVFrame* frame)
		{
			sdlRender->Display(frame->data,frame->linesize);
		}

		void playAudio(AVFrame* frame)
		{			
			if(swr)
			{
				int dstNbChannels = 1;
				int srcNbSamples = frame->nb_samples;
				int srcRate =inputContext->streams[audioIndex]->codec->sample_rate;
				int dstRate = inputContext->streams[audioIndex]->codec->sample_rate;
				int dstNbSamples = av_rescale_rnd(srcNbSamples, dstRate, srcRate, AV_ROUND_UP);
				AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_S16;

				uint8_t** dst_data = nullptr;
				int dstLinesize;
				dstNbChannels = av_get_channel_layout_nb_channels(inputContext->streams[0]->codec->channel_layout);
				dstNbChannels = dstNbChannels > 0 ? dstNbChannels : 1;
				int ret = av_samples_alloc_array_and_samples(&dst_data, &dstLinesize, dstNbChannels, dstNbSamples, dst_sample_fmt, 0);

				ret = swr_convert(swr, dst_data, dstNbSamples, (const uint8_t **)frame->data, srcNbSamples);

				sdlRender->PlaySamples((BYTE*)dst_data[0], dstLinesize);
				if (dst_data)
				{
					av_freep((void*)&dst_data[0]);
				}
				av_freep(&dst_data);
			}
			else
			{					
				sdlRender->PlaySamples(frame->data[0], frame->linesize[0]);
			}

		}
	private:
		AVFormatContext *inputContext;
		int64_t lastReadPacktTime ;
		int timeout;
		std::shared_ptr<CGSDLRender> sdlRender;
		std::thread task;
		int videoIndex;
		int audioIndex;
		CGPlayState playState;
		SwrContext* swr;
	};
}}};
#endif